/* Holly Beach & Liviu Patrasco
 *  cpu.c
 *  Created on: Feb 25, 2012
 */
#include <termios.h>            //termios, TCSANOW, ECHO, ICANON
#include <unistd.h>
#include "cpu.h"
#define MAX_CYCLES 70000000
#define TIMER_INTERRUPT_TIME_PERIOD_IN_SECONDS 1
#define TOTAL_MEMORY_LOCATIONS 50
#include <time.h>

struct timespec start, end;
//int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p);
int numberOfProcesses, numberBlockedProcs=0;
process *processes[MAX_PROCESSES];
process *currentProc;      //current running process
int memory[50];  //1 "memory" location
int pc;
char schedulingPolicy;
pthread_t timert, calcIO, kbIO;
void *runTimert(void *the_arg);
void *runCalcIO(void *the_arg);
void *runKBIO(void *the_arg);
volatile int timerflag;
volatile int calcIOBusyflag;
volatile int kbIOBusyflag;
volatile int calcIOInterruptSignal;
volatile int kbIOInterruptSignal;
int kbchar;
int exitFlag;
int c = 0;
int sizec = 0;
int summ = 0;
float avg = 0.0f;




void startth() {
	int rc;
	rc = pthread_create(&calcIO, NULL, runCalcIO, NULL);
	rc = pthread_create(&timert, NULL, runTimert, NULL);
	rc = pthread_create(&kbIO, NULL, runKBIO, NULL);
}
void *runTimert(void *the_arg) {
	timerflag = 1;

	while (1) {
		sleep(TIMER_INTERRUPT_TIME_PERIOD_IN_SECONDS);
		timerflag = 0;
	}

	pthread_exit(NULL);
	return NULL;
}
void *runCalcIO(void *the_arg) {
	while (1) {
		sleep(4);
		calcIOBusyflag = 0;
		calcIOInterruptSignal = 1;
	}
	pthread_exit(NULL);
	return NULL;
}
void *runKBIO(void *the_arg) {
	static struct termios oldt, newt;
	tcgetattr( STDIN_FILENO, &oldt);
	newt = oldt;
	newt.c_lflag &= ~(ICANON);
	tcsetattr( STDIN_FILENO, TCSANOW, &newt);

	while(1) {
		kbchar=getchar();
		//?

		kbIOBusyflag = 0;
		kbIOInterruptSignal = 1;
	}
	tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
	pthread_exit(NULL);
	return NULL;
}
void initializeCPU(process procs[MAX_PROCESSES], int numProcess, char schedPolicy){
	exitFlag = 0;
	int i;
	numberOfProcesses = numProcess;
	schedulingPolicy = schedPolicy;
	pc = 0;
	for (i=0; i<numberOfProcesses; i++){
		processes[i]= &procs[i];
	}
	for (i=0; i<TOTAL_MEMORY_LOCATIONS; i++){
		memory[i] = rand() % 21;  //memory value is random number between 0-20
	}
}
//int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
//{
//	return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
//			((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
//}
void runCPU(){
//	float i = 0;
	enum exitcode ec;
	clock_gettime(CLOCK_MONOTONIC, &start);
	if (currentProc == NULL) //first process to run
		currentProc = getProcess();
	while(1){
		currentProc->status = RUNNING;
		currentProc->strStatus = "RUNNING";
		switch(currentProc->name){
		case UI:
			ec = runUIProcess();
			break;
		case CALCULATOR:
			ec = runCalcProcess();
			break;
		case PRODUCER:
			ec = runProducerProcess();
			break;
		case CONSUMER:
			ec = runConsumerProcess();
			break;
		}
		if (calcIOInterruptSignal) {
			printf("FAKE ROUTER PRODUCED A FAKE INTERRUPT FOR THE FAKE CALCULATOR.\n");
			calcIOInterruptSignal = 0;
			wakeUpProcesses(CALCULATOR);
		} else if (kbIOInterruptSignal) {
			printf("KEYBOARD PRODUCED AN INTERRUPT.\n");
			kbIOInterruptSignal = 0;
			wakeUpProcesses(UI);
		}
		printProcessStates();

//      //For testing
//		uint64_t timeElapsed = timespecDiff(&end, &start);
//		if (timeElapsed >= 1000000000000000000LL) {
//			//printf("Time: %llu", timeElapsed);
//			summ += c;
//			sizec++;
//			i = (float)(summ / (float)sizec);
//			//printf("\n-----------------------Counter: %f\n\n", i); // printing stats about average number of context switches
//
//			c = 0;
//			clock_gettime(CLOCK_MONOTONIC, &start);
//		}
//		c++;

		if(ec == sysCall) {//a process was blocked
			contextSwitch();
			timerflag = 1;
		} else {
			timerflag = 1;
			timerSwitch();
			contextSwitch();
		}
		//clock_gettime(CLOCK_MONOTONIC, &end);



	}
	exit(0);
}

/*handles the interrupt from the timer*/
void timerSwitch(){
	//change status
	if(currentProc->status == RUNNING) {
		currentProc->status = RUNNABLE;
		currentProc->strStatus = "RUNNABLE";
	}

}
/*called when a running process blocks */
void contextSwitch() {
	currentProc->pc = pc; //save pc value
	currentProc = getProcess();
}

enum exitcode runUIProcess(){
	pc = currentProc->pc;
	while (timerflag && !calcIOInterruptSignal && !kbIOInterruptSignal){
		if (pc == currentProc->addressForSyscall){
			currentProc->status = BLOCKED;
			currentProc->strStatus = "BLOCKED";

			if (kbIOBusyflag) {
				printf("Process %d (%s) attempted to make a syscall to KEYBOARD. Device busy. Process blocked.\n",
						currentProc->id, currentProc->strName);
				return sysCall;
			}
			printf("Process %d (%s) made a system call to the keyboard I/O\n",
					currentProc->id, currentProc->strName);
			kbIOBusyflag = 1;
			pc++;
			return sysCall;
		}
		pc++;
		pc %= MAX_CYCLES;
	}

	return timerInt;
}

enum exitcode runCalcProcess(){
	pc = currentProc->pc;
	while (timerflag && !calcIOInterruptSignal && !kbIOInterruptSignal){
		if (pc == currentProc->addressForSyscall){
			currentProc->status = BLOCKED;
			currentProc->strStatus = "BLOCKED";

			if (calcIOBusyflag) {
				printf("Process %d (%s) attempted to make a syscall to ROUTER. Device busy. Process blocked.\n",
						currentProc->id, currentProc->strName);
				return sysCall;
			}
			calcIOBusyflag = 1;
			printf("Process %d (%s) made a system call to Router I/O device\n",
					currentProc->id, currentProc->strName);

			pc++;
			return sysCall;
		}
		pc++;
		pc %= MAX_CYCLES;
	}
	return timerInt;
}
enum exitcode runProducerProcess(){
	int rv;
	pc = currentProc->pc;
	while (timerflag && !calcIOInterruptSignal && !kbIOInterruptSignal) {
		if (pc == currentProc->addressForSyscall){
			rv = memorySemaphoreUp();
			if (rv != 0)
				return sysCall;
		}
		pc++;
		pc %= MAX_CYCLES;
	}
	return timerInt;
}
enum exitcode runConsumerProcess(){
	int rv;
	pc = currentProc->pc;
	while (timerflag && !calcIOInterruptSignal && !kbIOInterruptSignal) {
		if (pc == currentProc->addressForSyscall){
			rv = memorySemaphoreDown();
			if (rv != 0)
				return sysCall;
		}
		pc++;
		pc %= MAX_CYCLES;
	}
	return timerInt;
}

void wakeUpProcesses(enum process_names name){
	int i;
	for(i=0; i<numberOfProcesses; i++){
		if (processes[i]->name == name &&
				processes[i]->status == BLOCKED){
			processes[i]->status =	RUNNABLE;
			processes[i]->strStatus = "RUNNABLE";
		}
	}
}
int memorySemaphoreUp(){
	int i;
	printf("Process %d (%s) attempted to increment memory at address %d\n",
			currentProc->id, currentProc->strName, currentProc->memoryAddress);
	if (memory[currentProc->memoryAddress] == MAX_MEMORY_VALUE) {
		currentProc->status = BLOCKED;
		currentProc->strStatus = "BLOCKED";
		printf("Memory at address %d value at maximum, unable to increment\n", currentProc->memoryAddress);
		return -1;
	}
	memory[currentProc->memoryAddress]++;
	printf("Memory at address %d updated to %d\n",  currentProc->memoryAddress,
			memory[currentProc->memoryAddress]);
	if (memory[currentProc->memoryAddress]==1){ //wake up blocked consumers that access this memory location
		for(i=0; i<numberOfProcesses; i++){
			if (processes[i]->name == CONSUMER && processes[i]->status == BLOCKED
					&& processes[i]->memoryAddress == currentProc->memoryAddress){
				processes[i]->status =	RUNNABLE;
				processes[i]->strStatus = "RUNNABLE";
			}
		}
	}
	return 0;
}

int memorySemaphoreDown(){
	int i;
	printf("Process %d (%s) attempted to decrement memory at address %d\n",
			currentProc->id, currentProc->strName, currentProc->memoryAddress);
	if (memory[currentProc->memoryAddress] == 0) {
		currentProc->status = BLOCKED;
		currentProc->strStatus = "BLOCKED";
		printf("Memory at address %d value is 0, unable to decrement\n", currentProc->memoryAddress);
		return -1;
	}
	memory[currentProc->memoryAddress]--;
	printf("Memory at address %d updated to %d\n", currentProc->memoryAddress,
			memory[currentProc->memoryAddress]);
	if (memory[currentProc->memoryAddress] == MAX_MEMORY_VALUE - 1){
		for(i=0; i<numberOfProcesses; i++){
			if (processes[i]->name == PRODUCER && processes[i]->status == BLOCKED
					&& processes[i]->memoryAddress == currentProc->memoryAddress){
				processes[i]->status =	RUNNABLE;
				processes[i]->strStatus = "RUNNABLE";
			}
		}
	}
	return 0;
}
void printProcessStates(){
	int i;
	for(i=0; i<numberOfProcesses; i++){
		printf("  Process %d (%s)'s state: %s", processes[i]->id,
				processes[i]->strName, processes[i]->strStatus);
		if(schedulingPolicy == 'p' || schedulingPolicy == 'P')
			printf("  (Priority %d)", processes[i]->priority);
		printf("\n");
	}
	printf("\n");
}
